using System;
using System.Threading;

namespace Parlot.Fluent;

public abstract partial class Parser<T>
{
    private int _invocations;
    private volatile Parser<T>? _compiledParser;

    /// <summary>
    /// Gets or sets the text which is to render the textual representation of the parser.
    /// </summary>
    public string? Name { get; set; }

    public T? Parse(string text)
    {
        var context = new ParseContext(new Scanner(text));

        return Parse(context);
    }

    public T? Parse(string text, CancellationToken cancellationToken)
    {
        var context = new ParseContext(new Scanner(text), cancellationToken);

        return Parse(context);
    }

    public T? Parse(ParseContext context)
    {
        var localResult = new ParseResult<T>();

        var success = CheckCompiled(context).Parse(context, ref localResult);

        if (success)
        {
            return localResult.Value;
        }

        return default;
    }

    private Parser<T> CheckCompiled(ParseContext context)
    {
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER
        if (!System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported)
        {
            // NativeAOT supports System.Linq.Expressions via an interpreter, but the parser compilation code path is
            // fundamentally incompatible with trimming & AOT.
            // Unless .Compile is explicitly called elsewhere, trim it (and the interpreter) from the build.
            // See https://github.com/dotnet/runtime/blob/main/docs/design/tools/illink/feature-checks.md
            return this;
        }
#endif

        if (_compiledParser != null || context.CompilationThreshold == 0)
        {
            return _compiledParser ?? this;
        }

        // Only the thread that reaches CompilationThreshold compiles the parser.
        // Any other concurrent call here will return 'this'. This prevents multiple compilations of 
        // the same parser, and a lock.

        if (context.CompilationThreshold > 0 &&
            _invocations < context.CompilationThreshold &&
            Interlocked.Increment(ref _invocations) == context.CompilationThreshold)
        {
            return _compiledParser = this.Compile();
        }

        return this;
    }

    public bool TryParse(string text, out T? value)
    {
        return TryParse(text, out value, out _);
    }

    public bool TryParse(string text, CancellationToken cancellationToken, out T? value)
    {
        return TryParse(text, cancellationToken, out value, out _);
    }

    public bool TryParse(string text, out T value, out ParseError? error)
    {
        return TryParse(new ParseContext(new Scanner(text)), out value, out error);
    }

    public bool TryParse(string text, CancellationToken cancellationToken, out T value, out ParseError? error)
    {
        return TryParse(new ParseContext(new Scanner(text), cancellationToken), out value, out error);
    }

    public bool TryParse(ParseContext context, out T value, out ParseError? error)
    {
        error = null;

        try
        {
            var localResult = new ParseResult<T>();

            var success = CheckCompiled(context).Parse(context, ref localResult);

            if (success)
            {
                value = localResult.Value;
                return true;
            }
        }
        catch (ParseException e)
        {
            error = new ParseError
            {
                Message = e.Message,
                Position = e.Position
            };
        }
        catch (OperationCanceledException e)
        {
            error = new ParseError
            {
                Message = e.Message,
                Position = context.Scanner.Cursor.Position
            };
        }

        value = default!;
        return false;
    }

    public override string ToString() => $"{Name ?? GetType().Name}";
}
